﻿#include <algorithm>
#include <gtest/gtest.h>
#include <iostream>
#include <vector>

#define MEMSPY_IMPLEMENTATION
#include <xy/geom/vec.h>
#include <xy/topo/mesh.h>
#include <xy/topo/trimesh.h>
#include <xy/utils/memspy.h>

using namespace std;
using namespace xy;
using namespace xy::geom;
using namespace xy::topo;
using namespace xy::utils;

template <class MeshType> struct mesh3d
{
    MeshType topoMesh{};
    topo::property_handle<int> vertexId{"vid"};
    topo::property_handle<vec3f> vertexPos{"vpos"};
    topo::property_handle<vec3f> vertexNormal{"vnormal"};
    topo::property_handle<vec3f> faceNormal{"fnormal"};
    topo::property_handle<vec2f> vertexTexcoord{"vtexcoord"};

    /**
     * @brief 默认构造函数
     *
     */
    mesh3d() = default;

    /**
     * @brief 删除所有赋值和拷贝构造函数
     *
     */
    mesh3d(mesh3d &&) = delete;

    /**
     * @brief 要求顶点位置
     * @see
     */
    void request_vertex_positions()
    {
        for (auto v : topoMesh.vertices)
            v->add_property(vertexPos);
    }

    /**
     * @brief 移除顶点位置.
     * @see
     */
    void remove_vertex_positions()
    {
        for (auto v : topoMesh.vertices)
            v->remove_property(vertexPos);
    }

    /**
     * @brief 要求面法向.
     * @see
     */
    void request_face_normals()
    {
        for (auto f : topoMesh.faces)
            f->add_property(faceNormal);
    }

    /**
     * @brief 移除面法向.
     * @see
     */
    void remove_face_normals()
    {
        for (auto f : topoMesh.faces)
            f->remove_property(faceNormal);
    }

    /**
     * @brief 要求顶点法向
     * @see
     */
    void request_vertex_normals()
    {
        for (auto v : topoMesh.vertices)
            v->add_property(vertexNormal);
    }

    /**
     * @brief 移除顶点法向.
     * @see
     */
    void remove_vertex_normals()
    {
        for (auto v : topoMesh.vertices)
            v->remove_property(vertexNormal);
    }

    /**
     * @brief 创建顶点纹理坐标.
     * @see
     */
    void request_vertex_texcoords()
    {
        for (auto v : topoMesh.vertices)
            v->add_property(vertexTexcoord);
    }

    /**
     * @brief 移除顶点纹理坐标.
     * @see
     */
    void remove_vertex_texcoords()
    {
        for (auto v : topoMesh.vertices)
            v->remove_property(vertexTexcoord);
    }

    /**
     * @brief 创建顶点 ID.
     *
     */
    void request_vertex_ids()
    {
        for (auto v : topoMesh.vertices)
            v->add_property(vertexId);
    }

    /**
     * @brief 移除顶点 ID.
     *
     */
    void remove_vertex_ids()
    {
        for (auto v : topoMesh.vertices)
            v->remove_property(vertexId);
    }

    /**
     * @brief 计算面法向
     *
     */
    void update_face_normals()
    {
        for (auto f : topoMesh.faces)
        {
            auto coList = topo::shape_t::coedges_on_face(f);
            auto p0 = coList[0]->edge->start->property(vertexPos);
            auto p1 = coList[1]->edge->start->property(vertexPos);
            auto p2 = coList[2]->edge->start->property(vertexPos);
            f->property(faceNormal) = cross(p1 - p0, p2 - p1).normalized();
        }
    }

    /**
     * @brief 计算顶点法向 Gouraud 插值
     *
     */
    void update_vertex_normals()
    {
        for (auto v : topoMesh.vertices)
        {
            auto fList = topo::shape_t::adjacent_faces(v);
            if (fList.empty())
            {
                v->property(vertexNormal) = vec3f{};
                continue;
            }

            auto n = vec3f{};
            for (auto f : fList)
                n += f->property(faceNormal);

            v->property(vertexNormal) = n.normalized();
        }
    }
};

using genmesh3d = mesh3d<topo::mesh_t<0>>;
using trimesh3d = mesh3d<topo::trimesh>;

#define TIMES 10

class TestMesh : public ::testing::Test
{
  protected:
    void SetUp() override
    {
    }

    void TearDown() override {
        // nothing
    };

    void CreateFace3(genmesh3d &mesh, int lower, int higher, const std::vector<std::vector<coedge_t *>> &chandles,
                     std::vector<std::vector<int>> &tag)
    {
        for (int i = 0; i < tag.size(); i++)
            tag[i][i] = 1;

        for (int i = lower; i < higher; i++)
        {
            for (int j = lower; j < higher; j++)
            {
                for (int k = lower; k < higher; k++)
                {
                    if (tag[i][j] == 0 && tag[j][k] == 0 && tag[k][i] == 0)
                    {
                        tag[i][j] = 1;
                        tag[j][k] = 1;
                        tag[k][i] = 1;
                        mesh.topoMesh.create_face(chandles[i][j], chandles[j][k], chandles[k][i]);
                    }
                }
            }
        }
    }

    void CreateFace4(genmesh3d &mesh, int lower, int higher, const std::vector<std::vector<coedge_t *>> &chandles,
                     std::vector<std::vector<int>> &tag)
    {
        for (int i = 0; i < tag.size(); i++)
            tag[i][i] = 1;

        for (int i = lower; i < higher; i++)
        {
            for (int j = lower; j < higher; j++)
            {
                for (int k = lower; k < higher; k++)
                {
                    for (int l = lower; l < higher; l++)
                    {
                        std::set<int> s = {i, j, k, l};
                        if (s.size() == 4 && tag[i][j] == 0 && tag[j][k] == 0 && tag[k][l] == 0 && tag[l][i] == 0)
                        {
                            tag[i][j] = 1;
                            tag[j][k] = 1;
                            tag[k][l] = 1;
                            tag[l][i] = 1;
                            mesh.topoMesh.create_face(chandles[i][j], chandles[j][k], chandles[k][l], chandles[l][i]);
                        }
                    }
                }
            }
        }
    }

    void CreateFace5(genmesh3d &mesh, int lower, int higher, const std::vector<std::vector<coedge_t *>> &chandles,
                     std::vector<std::vector<int>> &tag)
    {
        for (int i = 0; i < tag.size(); i++)
            tag[i][i] = 1;

        for (int i = lower; i < higher; i++)
        {
            for (int j = lower; j < higher; j++)
            {
                for (int k = lower; k < higher; k++)
                {
                    for (int l = lower; l < higher; l++)
                    {
                        for (int m = lower; m < higher; m++)
                        {
                            std::set<int> s = {i, j, k, l, m};
                            if (s.size() == 5 && tag[i][j] == 0 && tag[j][k] == 0 && tag[k][l] == 0 && tag[l][m] == 0 &&
                                tag[m][i] == 0)
                            {
                                tag[i][j] = 1;
                                tag[j][k] = 1;
                                tag[k][l] = 1;
                                tag[l][m] = 1;
                                tag[m][i] = 1;
                                mesh.topoMesh.create_face(chandles[i][j], chandles[j][k], chandles[k][l],
                                                          chandles[l][m], chandles[m][i]);
                            }
                        }
                    }
                }
            }
        }
    }

    void CreateTotalTriangleMesh(genmesh3d &mesh, int N)
    {
        std::vector<vertex_t *> vhandles(N);
        for (int i = 0; i < N; i++)
            vhandles[i] = mesh.topoMesh.create_vertex();

        std::vector<int> indices(N);
        std::iota(indices.begin(), indices.end(), 0);

        std::vector<std::vector<coedge_t *>> chandles(N, std::vector<coedge_t *>(N));
        std::vector<std::vector<int>> tag(N, std::vector<int>(N, 0));
        for (int i = 0; i < N; i++)
        {
            for (int j = i + 1; j < N; j++)
            {
                auto co = mesh.topoMesh.create_edge(vhandles[indices[i]], vhandles[indices[j]]);
                chandles[i][j] = co;
                chandles[j][i] = co->twin;
            }
        }

        CreateFace3(mesh, 0, N, chandles, tag);
    }

    void CreateRandomTriangleMesh(genmesh3d &mesh, int N)
    {
        std::vector<vertex_t *> vhandles(N);
        for (int i = 0; i < N; i++)
            vhandles[i] = mesh.topoMesh.create_vertex();

        std::vector<int> indices(N);
        std::iota(indices.begin(), indices.end(), 0);
        std::shuffle(indices.begin(), indices.end(), std::mt19937{});

        int M = rand() % N;
        std::vector<std::vector<coedge_t *>> chandles(M, std::vector<coedge_t *>(M));
        std::vector<std::vector<int>> tag(M, std::vector<int>(M, 0));
        for (int i = 0; i < M; i++)
        {
            for (int j = i + 1; j < M; j++)
            {
                auto co = mesh.topoMesh.create_edge(vhandles[indices[i]], vhandles[indices[j]]);
                chandles[i][j] = co;
                chandles[j][i] = co->twin;
            }
        }

        int K = M == 0 ? 0 : rand() % M;
        CreateFace3(mesh, 0, K, chandles, tag);
    }

    void CreateRandomMixtureMesh(genmesh3d &mesh, int N)
    {
        std::vector<vertex_t *> vhandles(N);
        for (int i = 0; i < N; i++)
            vhandles[i] = mesh.topoMesh.create_vertex();

        std::vector<int> indices(N);
        std::iota(indices.begin(), indices.end(), 0);
        std::shuffle(indices.begin(), indices.end(), std::mt19937{});

        int M = rand() % N;
        std::vector<std::vector<coedge_t *>> chandles(M, std::vector<coedge_t *>(M));
        std::vector<std::vector<int>> tag(M, std::vector<int>(M, 0));
        for (int i = 0; i < M; i++)
        {
            for (int j = i + 1; j < M; j++)
            {
                auto co = mesh.topoMesh.create_edge(vhandles[indices[i]], vhandles[indices[j]]);
                chandles[i][j] = co;
                chandles[j][i] = co->twin;
            }
        }

        int K = M == 0 ? 0 : rand() % M;
        CreateFace3(mesh, 0, K / 3, chandles, tag);
        CreateFace4(mesh, K / 3, K / 2, chandles, tag);
        // CreateFace5(mesh, K / 2, K, chandles, tag);

        std::cout << "K: " << K << std::endl;
    }
};

// 全连通的网格
TEST_F(TestMesh, CreateTotal)
{
    GTEST_SKIP_("skip total mesh test");

    for (int i = 0; i < TIMES; i++)
    {
        memspy spy;

        // 使用指针控制提前析构，防止它在 spy 析构之后析构
        genmesh3d *mesh = new genmesh3d;
        CreateTotalTriangleMesh(*mesh, 100);
        delete mesh;
    }

    // std::cout << mesh.vertex_num() << std::endl;
    // std::cout << mesh.edge_num() << std::endl;
    // std::cout << mesh.face_num() << std::endl;
}

TEST_F(TestMesh, CreatePartial)
{
    GTEST_SKIP_("skip partial mesh test");

    for (int i = 0; i < TIMES; i++)
    {
        memspy spy;

        // 使用指针控制提前析构，防止它在 spy 析构之后析构
        genmesh3d *mesh = new genmesh3d;
        CreateRandomTriangleMesh(*mesh, 100);
        delete mesh;
    }

    for (int i = 0; i < TIMES; i++)
    {
        memspy spy;

        // 使用指针控制提前析构，防止它在 spy 析构之后析构
        genmesh3d *mesh = new genmesh3d;
        CreateRandomMixtureMesh(*mesh, 100);
        delete mesh;
    }

    // std::cout << mesh.vertex_num() << std::endl;
    // std::cout << mesh.edge_num() << std::endl;
    // std::cout << mesh.face_num() << std::endl;
}

TEST_F(TestMesh, RemoveTotal)
{
    GTEST_SKIP_("skip remove total mesh test");

    for (int i = 0; i < TIMES; i++)
    {
        memspy spy;

        // 使用指针控制提前析构，防止它在 spy 析构之后析构
        genmesh3d *mesh = new genmesh3d;
        CreateRandomMixtureMesh(*mesh, 100);

        // 根据前面两个测试创建的网格，随机删除一些顶点、边、面
        int N = mesh->topoMesh.vertices.size();
        int M = mesh->topoMesh.edges.size();
        int K = mesh->topoMesh.faces.size();

        N = rand() % N;
        M = M == 0 ? 0 : rand() % M;
        K = K == 0 ? 0 : rand() % K;

        while (!mesh->topoMesh.empty())
        {
            auto vit = mesh->topoMesh.vertices.begin();
            auto eit = mesh->topoMesh.edges.begin();
            auto fit = mesh->topoMesh.faces.begin();

            int tag = rand() % 3;
            if (tag == 0 && vit != mesh->topoMesh.vertices.end())
                mesh->topoMesh.remove(*vit);
            else if (tag == 1 && eit != mesh->topoMesh.edges.end())
                mesh->topoMesh.remove(*eit);
            else if (tag == 2 && fit != mesh->topoMesh.faces.end())
                mesh->topoMesh.remove(*fit);
        }

        delete mesh;
    }
}

TEST_F(TestMesh, Mesh3D)
{
    trimesh3d mesh;

    std::vector<vertex_t *> vhandles;
    vhandles.push_back(mesh.topoMesh.create_vertex());
    vhandles.push_back(mesh.topoMesh.create_vertex());
    vhandles.push_back(mesh.topoMesh.create_vertex());
    vhandles.push_back(mesh.topoMesh.create_vertex());

    // 注意创建完顶点后申请顶点位置属性
    mesh.request_vertex_positions();

    vhandles[0]->property(mesh.vertexPos) = vec3f{0.0f, 0.0f, 0.0f};
    vhandles[1]->property(mesh.vertexPos) = vec3f{1.0f, 0.0f, 0.0f};
    vhandles[2]->property(mesh.vertexPos) = vec3f{0.0f, 1.0f, 0.0f};
    vhandles[3]->property(mesh.vertexPos) = vec3f{0.0f, 0.0f, 1.0f};

    std::vector<face_t *> fhandles;
    fhandles.push_back(mesh.topoMesh.create_face(vhandles[0], vhandles[1], vhandles[2]));
    fhandles.push_back(mesh.topoMesh.create_face(vhandles[0], vhandles[3], vhandles[1]));

    // 测试顶点属性
    {
        property_handle<int> prop("test");
        vhandles[0]->add_property(prop);
        vhandles[0]->property(prop) = 10;
        EXPECT_EQ(vhandles[0]->property(prop), 10);
        EXPECT_TRUE(vhandles[0]->has_property(prop));

        vhandles[0]->remove_property(prop);
        EXPECT_FALSE(vhandles[0]->has_property(prop));
    }

    // 测试面属性
    {
        property_handle<int> prop("test");
        fhandles[0]->add_property(prop);
        fhandles[0]->property(prop) = 10;
        EXPECT_EQ(fhandles[0]->property(prop), 10);
        EXPECT_TRUE(fhandles[0]->has_property(prop));

        fhandles[0]->remove_property(prop);
        EXPECT_FALSE(fhandles[0]->has_property(prop));
    }

    // 测试顶点标记
    {
        mesh.request_vertex_ids();
        for (auto vh : mesh.topoMesh.vertices)
        {
            vh->property(mesh.vertexId) = 10;
        }
        mesh.remove_vertex_ids();
    }

    // 测试法向
    {
        mesh.request_vertex_normals();
        mesh.request_face_normals();
        mesh.update_face_normals();
        mesh.update_vertex_normals();
        mesh.remove_vertex_normals();
        mesh.remove_face_normals();
    }
}

TEST_F(TestMesh, TriangleMesh)
{
    trimesh3d mesh;

    std::vector<vertex_t *> vhandles;
    vhandles.push_back(mesh.topoMesh.create_vertex());
    vhandles.push_back(mesh.topoMesh.create_vertex());
    vhandles.push_back(mesh.topoMesh.create_vertex());
    vhandles.push_back(mesh.topoMesh.create_vertex());

    // 注意创建完顶点后申请顶点位置属性
    mesh.request_vertex_positions();

    vhandles[0]->property(mesh.vertexPos) = vec3f{0.0f, 0.0f, 0.0f};
    vhandles[1]->property(mesh.vertexPos) = vec3f{1.0f, 0.0f, 0.0f};
    vhandles[2]->property(mesh.vertexPos) = vec3f{0.0f, 1.0f, 0.0f};
    vhandles[3]->property(mesh.vertexPos) = vec3f{0.0f, 0.0f, 1.0f};

    std::vector<face_t *> fhandles;
    fhandles.push_back(mesh.topoMesh.create_face(vhandles[0], vhandles[1], vhandles[2]));
    fhandles.push_back(mesh.topoMesh.create_face(vhandles[0], vhandles[3], vhandles[1]));

    {
        auto nf = shape_t::adjacent_faces(vhandles[2]);
        auto it = std::find(nf.begin(), nf.end(), fhandles[0]);
        EXPECT_TRUE(it != nf.end());
        EXPECT_EQ(nf.size(), 1);
    }
}